#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Threading;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;

using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Math;
using JigLibX.Utils;
using JiggleGame.PhysicObjects;
using JiggleGame;
using JigLibX.Vehicles;
using System.Diagnostics;
#endregion

namespace GopacarsNetworkPrototype
{
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    class GameplayScreen : GameScreen
    {
        #region Fields

        PacketWriter packetWriter = new PacketWriter();
        PacketReader packetReader = new PacketReader();

        NetworkSession networkSession;
        ContentManager content;


        PhysicsSystem physicSystem;
        Model carModel, wheelModel, planeModel, terrainModel;
        
        //OLD
        SpriteBatch spriteBatch;
        SpriteFont gameFont;


        List<DrawableGameComponent> components = new List<DrawableGameComponent>();


        #endregion

        #region Properties


        /// <summary>
        /// The logic for deciding whether the game is paused depends on whether
        /// this is a networked or single player game. If we are in a network session,
        /// we should go on updating the game even when the user tabs away from us or
        /// brings up the pause menu, because even though the local player is not
        /// responding to input, other remote players may not be paused. In single
        /// player modes, however, we want everything to pause if the game loses focus.
        /// </summary>
        new bool IsActive
        {
            get
            {
                if (networkSession == null)
                {
                    // Pause behavior for single player games.
                    return base.IsActive;
                }
                else
                {
                    // Pause behavior for networked games.
                    return !IsExiting;
                }
            }
        }


        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public GameplayScreen(NetworkSession networkSession)
        {
            this.networkSession = networkSession;

            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
        }


        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(ScreenManager.GraphicsDevice);

            physicSystem = new PhysicsSystem();
            physicSystem.CollisionSystem = new CollisionSystemGrid(32, 32, 32, 30, 30, 30);
            //physicSystem.CollisionSystem = new CollisionSystemSAP();

            physicSystem.EnableFreezing = true;
            physicSystem.SolverType = PhysicsSystem.Solver.Normal;
            physicSystem.CollisionSystem.UseSweepTests = true;

            //Models
            carModel = content.Load<Model>("car");
            wheelModel = content.Load<Model>("wheel");
            planeModel = content.Load<Model>("plane");
            try
            {
                // some video card can't handle the >16 bit index type of the terrain
                terrainModel = content.Load<Model>("TesttrackHeightmap");
                HeightmapObject heightmapObj = new HeightmapObject(ScreenManager.Game, terrainModel, Vector2.Zero);
                components.Add(heightmapObj);
            }
            catch (Exception ex)
            {
                // if that happens just createa a ground plane 
                PlaneObject planeObj = new PlaneObject(ScreenManager.Game, planeModel, 15.0f);
                components.Add(planeObj);
            }

            //players
            int xpos = 5;
            foreach (NetworkGamer gamer in networkSession.AllGamers)
            {
                Player carObject1 = new Player(ScreenManager.Game, carModel, wheelModel, true, true, 30.0f, 5.0f, 4.7f, 5.0f, 0.20f, 0.4f, 0.05f, 0.8f, 0.30f, 1, 450.0f, physicSystem.Gravity.Length());
                carObject1.Car.Chassis.Body.MoveTo(new Vector3(-5 + xpos, 0, 5), Matrix.Identity);
                carObject1.Car.EnableCar();
                carObject1.Car.Chassis.Body.AllowFreezing = false;
                components.Add(carObject1);
                gamer.Tag = carObject1;
                xpos += 5;
            }

            gameFont = content.Load<SpriteFont>("gamefont");

        }

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            content.Unload();
        }


        #endregion

        #region Update


        /// <summary>
        /// Updates the state of the game.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            if (IsActive)
            {
                //if game is server, update all objects
                if (networkSession.IsHost)
                {
                    UpdateServer(gameTime);
                }
                //else if client, read positions from server
                else
                {
                    UpdateClient(gameTime);
                }
            }
            // If we are in a network game, check if we should return to the lobby.
            if ((networkSession != null) && !IsExiting)
            {
                if (networkSession.SessionState == NetworkSessionState.Lobby)
                {
                    LoadingScreen.Load(ScreenManager, true,
                                       new BackgroundScreen(),
                                       new LobbyScreen(networkSession));
                }
            }
        }

        /// <summary>
        /// The server's update method.
        /// Read input from players.
        /// Run physics simulation and send all positions to clients.
        /// </summary>
        /// <param name="gameTime"></param>
        private void UpdateServer(GameTime gameTime)
        {
            // Read any incoming network packets.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
            // Keep reading as long as incoming packets are available.
                while (gamer.IsHost && gamer.IsDataAvailable)
                {
                    NetworkGamer sender;

                    // Read a single packet from the network.
                    gamer.ReceiveData(packetReader, out sender);

                    if (!sender.IsLocal)
                    {
                        // Look up the Player associated with whoever sent this packet.
                        Player p = sender.Tag as Player;
                        p.nextSteerAction = (Player.PlayerSteerActions) packetReader.ReadInt32();
                        p.nextPedalAction = (Player.PlayerPedalActions)packetReader.ReadInt32();
                    }
                }
            }
            //just point current camera at local player 0 for now
            Player localCar = (Player)networkSession.LocalGamers[0].Tag;
            Camera currentCamera = ((GopacarsNetworkPrototypeGame)ScreenManager.Game).CurrentCamera;
            currentCamera.Position = new Vector3(localCar.PhysicsBody.Position.X + 25, currentCamera.Position.Y, localCar.PhysicsBody.Position.Z + 25);

            physicSystem.Integrate((float)1.0f / 60.0f);


            // First off, our packet will indicate how many tanks it has data for.
            packetWriter.Write(networkSession.AllGamers.Count);

            foreach (NetworkGamer gamer in networkSession.AllGamers)
            {
                Player p = gamer.Tag as Player;
                p.Update(gameTime);

                // Write the state into the output network packet.
                packetWriter.Write( p.PhysicsBody.Position );
            }

            // Send the combined data for all tanks to everyone in the session.
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;

            server.SendData(packetWriter, SendDataOptions.InOrder);
        
        }

        /// <summary>
        /// The clients' update method, only runs on clients (not server).
        /// Send player actions to server
        /// Read positions from network.
        /// </summary>
        /// <param name="gameTime"></param>
        private void UpdateClient(GameTime gameTime)
        {
            // Read any incoming network packets.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                //SEND ACTIONS
                Player p = gamer.Tag as Player;
                packetWriter.Write((int)p.nextSteerAction);
                packetWriter.Write((int)p.nextPedalAction);
                p.nextSteerAction = Player.PlayerSteerActions.NONE;
                p.nextPedalAction = Player.PlayerPedalActions.NONE;

                // Send our input data to the server.
                gamer.SendData(packetWriter, SendDataOptions.InOrder, networkSession.Host);


                //READ Positions
                //Keep reading as long as incoming packets are available.
                while (gamer.IsDataAvailable)
                {
                    NetworkGamer sender;

                    // Read a single packet from the network.
                    gamer.ReceiveData(packetReader, out sender);

                    // If a player has recently joined or left, it is possible the server
                    // might have sent information about a different number of players
                    // than the client currently knows about. If so, we will be unable
                    // to match up which data refers to which player. The solution is
                    // just to ignore the packet for now: this situation will resolve
                    // itself as soon as the client gets the join/leave notification.
                    if (networkSession.AllGamers.Count != packetReader.ReadInt32())
                        continue;

                    // This packet contains data about all the players in the session.
                    foreach (NetworkGamer remoteGamer in networkSession.AllGamers)
                    {
                        p = remoteGamer.Tag as Player;

                        // Read the state of this player from the network packet.
                        p.PhysicsBody.Position = packetReader.ReadVector3();
                    }
                }
            }
        }


        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input.PauseGame)
            {
                // If they pressed pause, bring up the pause menu screen.
                ScreenManager.AddScreen(new PauseMenuScreen(networkSession));
            }
            else
            {
                // Read inputs for all locally controlled player.
                foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
                {
                    Player p = gamer.Tag as Player;

                    if (input.CurrentKeyboardStates[(int)gamer.SignedInGamer.PlayerIndex].IsKeyDown(Keys.Right))
                    {
                        p.nextSteerAction = Player.PlayerSteerActions.RIGHT;
                    }
                    else if (input.CurrentKeyboardStates[(int)gamer.SignedInGamer.PlayerIndex].IsKeyDown(Keys.Left))
                    {
                        p.nextSteerAction = Player.PlayerSteerActions.LEFT;
                    }

                    if (input.CurrentKeyboardStates[(int)gamer.SignedInGamer.PlayerIndex].IsKeyDown(Keys.Down))
                    {
                        p.nextPedalAction = Player.PlayerPedalActions.BREAK;
                    }
                    else if (input.CurrentKeyboardStates[(int)gamer.SignedInGamer.PlayerIndex].IsKeyDown(Keys.Up))
                    {
                        p.nextPedalAction = Player.PlayerPedalActions.ACCELERATE;
                    }

                }
            }
        }

        #endregion

        #region Draw

        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // This game has a blue background. Why? Because!
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
                                               Color.CornflowerBlue, 0, 0);



            spriteBatch.Begin();

            // For each person in the session...
            foreach (NetworkGamer gamer in networkSession.AllGamers)
            {
                // Look up the Player object belonging to this network gamer.
                CarObject p = gamer.Tag as CarObject;
                p.Draw(gameTime);
            }


            foreach (DrawableGameComponent component in components)
            {
                component.Draw(gameTime);
            }

            if (networkSession != null)
            {
                string message = "Players: " + networkSession.AllGamers.Count;
                Vector2 messagePosition = new Vector2(100, 480);
                spriteBatch.DrawString(gameFont, message, messagePosition, Color.White);
            }

            spriteBatch.End();

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0)
                ScreenManager.FadeBackBufferToBlack(255 - TransitionAlpha);
        }


        #endregion
    }
}
